//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Execution
{
    using System;
    using System.IO;
    using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
    using Microsoft.Research.eResearch.Common;
    using System.Reflection;

    /// <summary>
    /// Proxy implementation for workflow execution.  Executor is created inside the app domain.
    /// </summary>
    public sealed class JobExecutorProxy : MarshalByRefObject
    {
        /// <summary>
        /// Current workflow job Id.
        /// </summary>
        private Guid jobId;

        /// <summary>
        /// Target folder where the shadow copy of the executor dlls are stored.
        /// </summary>
        private string targetFolder;

        /// <summary>
        /// Library folder where the downloaded dlls are stored.
        /// </summary>
        private string libFolder;

        /// <summary>
        /// Initializes a new instance of the JobExecutorProxy class.
        /// </summary>
        public JobExecutorProxy()
        {
        }

        /// <summary>
        /// Initializes a new instance of the JobExecutorProxy class.
        /// </summary>
        /// <param name="jobId">Current workflow job id.</param>
        public JobExecutorProxy(Guid jobId)
        {
            this.Init(jobId);
        }

        /// <summary>
        /// Executes the workflow job.
        /// </summary>
        public int Execute(bool isLightWeightExecution)
        {
            int jobResult = -1;
            AppDomain executorDomain = null;

            ExceptionHandler.Handle(
            PolicyName.LoggingPolicy,
            () =>
            {
                // Create sandbox folder.
                CreateSandboxFolder();

                // Copy the executor and dependent assmeblies to sandbox folder.
                CopyTargetAssemblies();

                // Create AppDomain and add unhandled exception delegate.
                executorDomain = CreateAppDomain();

                // Create JobExecutorProxy inside the appdomain.
                JobExecutorProxy jobExecutorProxy = executorDomain.CreateInstanceAndUnwrap(
                    System.IO.Path.GetFileNameWithoutExtension(typeof(JobExecutorProxy).Assembly.Location),
                    typeof(JobExecutorProxy).FullName) as JobExecutorProxy;

                // Equvalent to constructor.
                jobExecutorProxy.Init(jobId);

                // Execute the job.
                jobResult = jobExecutorProxy.ExecuteDelegate(isLightWeightExecution);
            },
            () =>
            {
                // Unload appdomain.
                UnloadAppDomain(executorDomain);

                // Delete the sandbox folder.
                DeleteSandbox();
            });
            return jobResult;
        }

        /// <summary>
        /// Unloads the executor app domain.
        /// </summary>
        /// <param name="executorDomain">Instance of the AppDomain.</param>
        private static void UnloadAppDomain(AppDomain executorDomain)
        {
            try
            {
                if (executorDomain != null)
                {
                    AppDomain.Unload(executorDomain);
                }
            }
            catch (CannotUnloadAppDomainException) { }
        }

        /// <summary>
        /// Method logs the unhandled exception in the app domain.
        /// </summary>
        /// <param name="sender">Object who raises the unhandled exception.</param>
        /// <param name="e">Event argument containing exception details.</param>
        private void ExecutorDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            ExceptionPolicy.HandleException(e.ExceptionObject as Exception, PolicyName.LoggingPolicy);
        }

        /// <summary>
        /// Creates the folder with job id as the name in the temporary location.
        /// </summary>
        private void CreateSandboxFolder()
        {
            if (!Directory.Exists(targetFolder))
            {
                Directory.CreateDirectory(targetFolder);
                Directory.CreateDirectory(libFolder);
            }
        }

        /// <summary>
        /// Shadow copies the executor assemblies to the target folder.
        /// </summary>
        private void CopyTargetAssemblies()
        {
            string searchFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            foreach (string file in Directory.GetFiles(searchFolder, "*.dll"))
            {
                File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file)), true);
            }

            foreach (string file in Directory.GetFiles(searchFolder, "*.xml"))
            {
                File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file)), true);
            }

            foreach (string file in Directory.GetFiles(searchFolder, "*.configSources"))
            {
                File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file)), true);
            }
        }

        /// <summary>
        /// Creates a new app domain with target folder and lib subfolder for resolution.
        /// </summary>
        /// <returns>New instance of the app domain.</returns>
        private AppDomain CreateAppDomain()
        {
            AppDomain executorDomain = AppDomain.CreateDomain("JobExecutorDomain", null, targetFolder, "lib", false);
            executorDomain.UnhandledException += new UnhandledExceptionEventHandler(ExecutorDomainUnhandledException);
            return executorDomain;
        }

        /// <summary>
        /// Deletes the job folder.
        /// </summary>
        private void DeleteSandbox()
        {
            try
            {
                Directory.Delete(targetFolder, true);
            }
            catch (UnauthorizedAccessException) { }
            catch (IOException) { }
        }

        /// <summary>
        /// Initializes the proxy instance.
        /// </summary>
        /// <param name="id">Job id to be initialized.</param>
        private void Init(Guid id)
        {
            this.jobId = id;
            this.targetFolder = Path.Combine(Path.GetTempPath(), id.ToString() + "\\");
            this.libFolder = Path.Combine(this.targetFolder, "lib\\");
        }

        /// <summary>
        /// Executes the original Job executor.
        /// </summary>
        private int ExecuteDelegate(bool isLightWeightExecution)
        {
            using (JobExecutor executor = new JobExecutor())
            {
                executor.Execute(jobId, targetFolder, libFolder, isLightWeightExecution);
                return executor.JobResult;
            }

        }
    }
}
